<script setup lang="tsx">
import { nextTick, reactive, ref, computed, watch, watchEffect, createApp, provide, defineAsyncComponent, type VNodeRef } from "vue";
import Son from "./component/Son.vue";
import LabelSelect from "./component/LabelSelect.vue";
import TextHighlight from "./component/TextHighlight.vue";
import Form from "./component/Form.vue";

// ------------------------------------------------------------------------ 响应式变量生命 reactive 和 ref
/*
Virtual script "d:/workArea/Me/HomeWork/vue3/src/App.vue.tsx" not found, may missing <script lang="ts"> / "allowJs": true / jsconfig.json.
reactive():

   想要在组件模板中使用响应式变量 需要使用setup 函数返回

   const state = reactive({ count: 0 });
       - 通过reactive 函数创建 响应式变量（数组 对象）

   let { cont } = state
   cont++
       - 如果将响应式重新 赋值 解构 实参 那么它就失去了响应式链接

ref():

   由于reactive 局限性 vue 提供了 ref可以做到任何值类型都响应式，
   ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象：

   const count = ref(0)
   console.log(count.value) // 0
   count.value++
   console.log(count.value) // 1
  
   在组件模板中ref 则不需要使用.value 直接 cont  它们会被自动“解包”
   如果觉得使用ref每次都得.value 可以直接使用$ref （前提是在vite.config.ts中进行配置，在编译时他会自动帮我们转换）

*/

const state = reactive({ count: 0, name: "张三" }) as any;

// $ref 不用每次都.value
const num = $ref({ count: 0 });

// 深层对象可以响应链接
const object = { foo: ref(1) } as any;

// 在方法中使用ref 需要.value ,组件模板中则不需要
function increment() {
   state.count++;
   let { foo } = object;
   console.log(foo.value++);
}

// ------------------------------------------------------------------- 监听  计算属性 与 watch
// computed
const author = reactive({
   name: "John Doe",
   books: ["Vue 2 - Advanced Guide", "Vue 3 - Basic Guide", "Vue 4 - The Mystery"],
});

// 一个计算属性 返回一个 ref 响应式的数据 （理解为定义一个会自动计算的变量）
const publishedBooksMessage = computed(() => {
   console.log("计算属性 发生了 改变");
   // 自动跟踪响应式 依赖（author）计算属性的值会被缓存 当依赖变换时才会更新缓存，只是一个快照不可修改 不可在内部使用副作用
   return author.books.length;
});

const firstName = ref("John");
const lastName = ref("Doe");

// 计算属性可写方式（get set ）
const fullName = computed({
   // getter
   get() {
      console.log("computed getFun");

      return firstName.value + " " + lastName.value;
   },
   // setter
   set(newValue: any) {
      console.log("computed getFun", newValue);

      // 注意：我们这里使用的是解构赋值语法
      [firstName.value, lastName.value] = newValue.split("");
   },
});
// --------
// wacth 惰性监听
const x = ref(0);
const y = ref(0);
// 参数1 ： 任何形式的数据源 如：x || [x,y] || ()=>x
// 参数2 ： 当监听的值发生改变的时触发

// 单个 ref
watch(x, (newX) => {
   console.log(`x is ${newX}`);
});

// 监听一个getter 函数
watch(
   () => x.value + y.value,
   (newNum, oldNum) => {
      // 当question 值改变时就会触发
      console.log("question值发生了改变！~~", newNum);
   }
);

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
   console.log(`x is ${newX} and y is ${newY}`);
});

// 当watch 监听一个对象的时候
const obj = reactive({
   count: 0,
   sex: 4,
   info: { name: "张三", age: 567 },
});

watch(
   // obj,  // 监听一个对象 默认开启深层监听
   () => obj.info, // 监听一个深层对象 需要手动开启深层监听
   (newValue, oldValue) => {
      console.log("watch obj chnage", newValue, oldValue);
      // 当监听一个对象的时候 无论你修改任意一个层级的值 newValue, oldValue 值都是一致的
      // 当监听一个深层对象时 不会触发改函数 只有当这个对象被修改时才会触发 或者 开启深层监听 deep：；true
   },
   { deep: true } // 如果需要 监听深层的对象中某一个值时 则需要开启这个属性
);

// warchEffect
// watch() 是懒执行的：仅当数据源变化时，才会执行回调,如果想要watch 在初始化执行一次的话 就需要使用watchEffect

// --区别
// wacth 能够更明确的监听指定的值
// warchEffect 自动监听所有响应式数据

// -- 停止监听器
// 当监听器异步创建的时候它就会与组件失去关联 需要手动 解除
// 解除方法 - 调用 watch || watchEffect 返回的函数

watchEffect(async () => {
   const response = await fetch(author.name);
   author.books = await response.json();
});
// ------------------------------------------------------------------------ 组件
// 注册
// 全局注册 app.component() 方法，支持链式调用 。让组件在当前 Vue 应用中全局可用。
// const app = createApp({});
// app.component('MyComponent',组件名)
// 局部注册 在单个文件中引入 直接使用
// 区别 ： 全局注册在生产环境时 即使没有使用 只要注册了 都打包进去
//         局部注册 按需使用 生产环境使用时才会被打包
function setBooks() {
   console.log("setBoos触发");

   author.books = author.books.filter((item) => item.indexOf("Guide") != -1);
}

function test() {
   console.log("test触发");
}

//  -------------------------------- --依赖注入
// 通常情况下 深层组件想要访问顶层组件的数据时 ，需要顶层的组件一层一层的往下传递
// 这让中间一下不需要使用该数据的组件进行了接受 这就很难受。
// Provide (提供) 和 Inject (注入) 可以帮助我们解决这一问题
// 这有点类似全局对象 但是不同的时候 它的数据是可以响应式的 当改变时 其他用到它的地方也会响应刷新！！！！！！

const count = ref(10);
provide(/* 注入名 */ "num", /* 值 */ count);

// ------------------------------------ 异步组件
// 当我们需要拆分应用为更小的块，并仅在需要时再从服务器加载相关组件。按需加载组件
const NewSon = defineAsyncComponent(() => import("./component/Son.vue"));
// defineAsyncComponent 方法接收一个返回 Promise 的加载函数。
// 在这里 import() 方法返回的也是一个 promise 对象
// -- defineAsyncComponent返回一个外层包装过的组件，它会将接收到的 props 和插槽传给内部组
//     跟原来的组件没有任何区别 ，同时实现延迟加载。

// ----------------------------------自定义指令
// 在模板中启用 v-focus
// 在 <script setup> 中，任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令
// 以下写法只能在当前组件（界面）中使用
const vFocus = {
   // ------- 当元素绑定改指令时执行的一些周期函数
   // 在绑定元素的 attribute 前
   // 或事件监听器应用前调用
   created(el, binding, vnode, prevVnode) {
      // 下面会介绍各个参数的细节 建议官网 https://cn.vuejs.org/guide/reusability/custom-directives.html#directive-hooks
   },
   // 在元素被插入到 DOM 前调用
   beforeMount(el, binding, vnode, prevVnode) {},
   // 在绑定元素的父组件
   // 及他自己的所有子节点都挂载完成后调用
   mounted: (el: any) => el.focus(),
   // 绑定元素的父组件更新前调用
   beforeUpdate(el, binding, vnode, prevVnode) {},
   // 在绑定元素的父组件
   // 及他自己的所有子节点都更新后调用
   updated(el, binding, vnode, prevVnode) {},
   // 绑定元素的父组件卸载前调用
   beforeUnmount(el, binding, vnode, prevVnode) {},
   // 绑定元素的父组件卸载后调用
   unmounted(el, binding, vnode, prevVnode) {},
};

//  定义全局focus指令
const app = createApp({});

// 使 v-focus 在所有组件中都可用 生命周期钩子 都时对应一样的
app.directive("focus", {
   /* ... */
});

console.log("state.contstate.contstate.cont", state.count);
const showPicker = ref(false);
const show = ref(false);

const formRef = ref<VNodeRef>();

// form表单绑定的字段
const feilds = ref({
   name: "11",
   age: "2",
   checkbox: ["1", "2"],
   selectPicker: "",
   code: "",
   order: "",
}) as any;

// 生成form表单的数据
const formList = ref([
   {
      type: "input",
      name: "name",
      label: "用户名",
      placeholder: "用户名",
   },
   {
      type: "input",
      name: "code",
      label: "身份证",
      placeholder: "请输入身份证",
   },
   {
      type: "input",
      name: "order",
      label: "单号",
      placeholder: "请输入单号",
   },
   {
      type: "radio",
      name: "age",
      label: "用户名",
      placeholder: "用户名",
      radioList: [
         {
            label: "男",
            value: "1",
         },
         {
            label: "女",
            value: "2",
         },
      ],
   },
   {
      type: "checkbox",
      name: "checkbox",
      label: "复选框组",
      placeholder: "复选框组",
      checkboxList: [
         {
            label: "复选框1",
            value: "1",
         },
         {
            label: "复选框2",
            value: "2",
         },
      ],
   },
   {
      type: "picker",
      name: "selectPicker",
      label: "选择器",
      placeholder: "点击选择城市",
      onClick() {
         showPicker.value = true;
      },
   },
]);

const aa = () => {
   console.log(feilds);
};

function onConfirm({ selectedValues }: any) {
   feilds.value.selectPicker = selectedValues[0];
   showPicker.value = false;
   console.log(feilds);
}

function submit(values: any) {
   console.log("submit", values);
   console.log(formRef.value);
}

function returnHtmml() {
   return <h1>8888</h1>;
}
</script>

<template>
   <div>
      bushi
      <div v-for="item in author.books" :key="item">
         {{ item }}
      </div>
      <hr />
      <div class="content" @click="state.name = '你可真是个小垃圾'">state:{{ state.count }}</div>
      <div class="content">state:{{ state?.name }}</div>
      <div>useRef:{{ object.foo }}</div>
      <div>计算属性 :{{ publishedBooksMessage }}</div>
      <div>fullName:{{ fullName }}</div>

      <button @click="increment">count ++</button>

      <button @click="setBooks">Set author.hooks</button>

      <button @click="() => (fullName = 'aa')">set firstName</button>
      <hr />
      <p>x:{{ x }}</p>
      <button @click="() => (x += 10)">watch Click question</button>
      <button @click="() => (y += 1)">watch Click X or Y</button>
      <button @click="() => (obj.info.age += 1)">watchObj Click</button>
      <hr />

      <Son :count="num.count" ref="aa" title="Vue3的学习笔记" class="test" @setBooks="setBooks" @click="test">
         <template #default="{ sex }" style="color: red; font-weight: bold">{{ sex }}==这里是 组件标签中的内容</template>

         <!-- 
      ------------------------------------插槽
      插槽 插槽 就是我们组件标签中所包含的东西 最后在通过 slot 标签将包含的内容回显到该组件里。

      //---- Son.vue
      <template>
        <div>
          <slot></slot>  //-- 此时的slot标签就是等价于 Father.vue Son标签中内容
          <hr/>
          <slot name="header"></slot>  // -- 对应 <template #header> 里的内容
        </div>
      </template>

      //---- Father.vue
      // import Son from "Son.vue"
      <template>
        <div>
            <Son>
              <h3>这里son组件标签的内容</h3>

              --当然我们也可以指定某一块内容，插入到某一个地方 这就是 具名插槽。
                需要注意的是 具名插槽的内容 需要使用 <template> 标签包裹，而默认的则不需要 --
              
              <template #header>   -- 通过 v-slot 指令来声名插槽名字   v-slot = "header"  简写 ===> #header
                header 插槽的内容
              </template>

            </Son>
        </div>
      </template>

      -------------------------------作用域插槽
      - 我们的插槽内容都是写在父组件文件里，它可以直接访问父组件的变量，某些情况些我们希望它也可以访问子组件的内容
        我们可以在 <slot> 标签 绑定需要传递的值 最后在插槽上通过 v-slot 来接收，这有点类似组件传值

      //---- Son.vue
      <template>
        <div>
          <slot age="45"></slot> 
          <hr/>
        </div>
      </template>

      //---- Father.vue
      // import Son from "Son.vue"
      <template>
        <div>
            <Son v-slot="slotProps">
              <h3>这里son组件标签的内容</h3>
              {{slotProps.age}}  -- 通过 v-slot 指令来接收绑定在 slot 上除了name 以外的属性
            </Son>
        </div>
      </template>
      
      注意点：每一个 <slot> 组件对应一个 <template> 插槽 他们都是独立的，
              你不能在 headder 插槽内访问 flooter 插槽传递的内容 ！！

              v-slot 指令 只有当你的组件内 只有默认插槽的时才可以写在
              组件标签上，如果存在多个插槽时你需要转变一下写法将v-slot
              写在对应插槽的 <template> 上 。

              语法： <template v-slot:插槽名="接受slot属性">
                    <template v-slot:header="headSlotProps">
      -->
         <template v-slot:header="{ count }">这里是具名插槽的内容--{{ count }}</template>
      </Son>

      <Button @click="count++">改变Provide提供的值</Button>
      <hr />

      <!-- 异步组件 -->
      <NewSon />

      <h3>自定义指令-------------------</h3>
      <!-- 效果 组件挂在时自动聚焦 -->
      <input type="text" v-focus />

      <h3>vue 内置组件 Transition -------------------</h3>
      <button @click="show = !show">Toggle</button>
      <Transition>
         <p v-if="show">hello</p>
      </Transition>
      <!-- 
  --------------- vue 内置组件 功能简介
  // https://cn.vuejs.org/guide/built-ins/transition.html#css-based-transitions

  ==================  Transition  
  // 给默认插槽的内容添加动画 Eeter（进入） Leave（离开）
  
   ================== TransitionGroup
  // 跟 Transition 效果是一样的 但是作用于列表的某一项  Eeter（进入） Leave（离开）

   ================== KeepAlive
  // 当一个组件 切换 消失 出现时 如果想要其状态保留 就可以使用KeepAlive 缓存

  ==================== Teleport 
  // 将默认插槽的内容挂在到 Teleport to 属性指定的dom上 但不影响父子组件之间的关系
  
 -->
      <RouterView />
   </div>
</template>

<style lang="scss" scoped>
.content {
   color: red;
}

.v-enter-active,
.v-leave-active {
   transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
   opacity: 0;
}
</style>
